package com.hjy.servlet;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.hjy.annotation.RequestBody;
import jdk.internal.org.objectweb.asm.Type;
import org.objectweb.asm.*;
import cn.hutool.json.JSONUtil;
import com.hjy.annotation.ResponseBody;
import com.hjy.handler.Handler;
import com.hjy.handler.HandlerMapping;
import jdk.internal.org.objectweb.asm.Opcodes;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class DispatchServlet extends HttpServlet {

    private static HandlerMapping handlerMapping = new HandlerMapping();

    /**
     * init 方法在每次进入这个servlet都会执行
     *
     * @throws ServletException
     */
    @Override
    public void init() throws ServletException {
        // 单例模式
        if (handlerMapping.mapping.isEmpty()) {
            // 初始化其 controller bean
            SAXReader saxReader = new SAXReader();
            List<Object> beanList = new ArrayList<>();
            try {
                // 加载配置文件位置
                Document document = null;
                String contextConfigLocation = getServletContext().getInitParameter("contextConfigLocation");
                if (StrUtil.isBlank(contextConfigLocation)) {
                    document = saxReader.read(getClass().getClassLoader().getResourceAsStream("HjyMvc.xml"));
                } else {
                    document = saxReader.read(getClass().getClassLoader().getResourceAsStream(contextConfigLocation));
                }
                Element rootElement = document.getRootElement();
                // 获取所有的 bean
                List<Element> beans = rootElement.elements("bean");
                for (Element bean : beans) {
                    // 获取类路径
                    String classLocation = bean.attributeValue("class");
                    // 获取到了对应的 controller 对象
                    Object object = Class.forName(classLocation).newInstance();
                    beanList.add(object);
                }
                System.out.println("beanList = " + beanList);
                // 开始处理映射
                handlerMapping.process(beanList);
                System.out.println("handlerMapping = " + handlerMapping.mapping);
            } catch (DocumentException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
        String requestURI = req.getRequestURI();
        // 项目上下文路径
        String contextPath = req.getContextPath();
        // 去掉项目上下文路径 默认为 ""
        String path = requestURI.substring(contextPath.length());
        System.out.println("path = " + path);
        // 获取handler对象, 使用反射执行方法
        Handler handler = handlerMapping.getMapping(path);

        Object bean = handler.getObject();
        Method method = handler.getMethod();
        // 判空
        if (method == null || bean == null) {
            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write("对不起, 没有对应" + path + "的接口");
        }
        // 执行, 并获取结果
        // 1. 如果有 ResponseBody 注解
        if (bean.getClass().getAnnotation(ResponseBody.class) != null || method.getAnnotation(ResponseBody.class) != null) {
            Object result = getProxyResult(bean, method, req, resp);
            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write(JSONUtil.toJsonStr(result));
        } else {
            // 看是 重定向还是转发, 默认转发
            Object result = getProxyResult(bean, method, req, resp);
            String s = result.toString();
            if (s.startsWith("redirect:")) {
                s = s.substring("redirect:".length());
                resp.sendRedirect(contextPath + s);
            } else {
                if (s.startsWith("forward:")) {
                    s = s.substring("forward:".length());
                }
                // 转发出去
                req.getRequestDispatcher(contextPath + s).forward(req, resp);
            }

        }

    }

    /**
     * 将方法注入参数, 返回执行后的结果
     *
     * @param bean
     * @param method
     * @return
     */
    public Object getProxyResult(Object bean, Method method, HttpServletRequest req, HttpServletResponse resp) {
        try {
            Class<?>[] parameterTypes = method.getParameterTypes();

            if (parameterTypes.length <= 0) {
                return method.invoke(bean);
            } else {
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                String[] parameter1 = getMethodParameterNamesByAsm4(bean.getClass(), method);
                // 注入参数
                Object[] parameters = new Object[parameterTypes.length];
                for (int i = 0; i < parameterTypes.length; i++) {
                    Boolean flag = false;
                    // 是否使用 JSON 方式绑定参数
                    for (int j = 0; j < parameterAnnotations[i].length; j++) {
                        // 获取注解类型进行比对, 注意这里parameterAnnotations中全是代理类型... 直接getClass 会报错
                        if (parameterAnnotations[i][j].annotationType() == RequestBody.class) {
                            Object o = JSONUtil.toBean(IoUtil.read(req.getInputStream(), Charset.defaultCharset()), parameterTypes[i]);
                            parameters[i] = o;
                            flag = true;
                            break;
                        }
                    }
                    if (flag) {
                        continue;
                    }
                    if (parameterTypes[i] == HttpServletRequest.class) {
                        parameters[i] = req;
                    }
                    if (parameterTypes[i] == HttpServletResponse.class) {
                        parameters[i] = resp;
                    }
                    if (isWrapper(parameterTypes[i])) {
                        parameters[i] = setWrapper(req, parameter1[i], parameterTypes[i]);
                    }
                }

                return method.invoke(bean, parameters);
            }

        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public Boolean isWrapper(Class myClass) {
        // 自行补充吧
        if (myClass == String.class || myClass == Integer.class || myClass == Long.class) {
            return true;
        }
        return false;
    }

    /**
     * 设置值
     *
     * @param request
     * @param name
     * @param type
     * @return
     */
    public Object setWrapper(HttpServletRequest request, String name, Class type) {
        // 从 request 中获取参数
        Object o = null;
        o = request.getParameter(name);
        if (o == null) {
            o = request.getAttribute(name);
        }
        if (o != null) {
            if (type == String.class) {
                o = o.toString();
            } else if (type == Integer.class) {
                o = Integer.parseInt(o.toString());
            } else if (type == Long.class) {
                o = Long.parseLong(o.toString());
            }
        }
        return o;
    }

    /**
     * 根据类和方法获取参数名称
     * @param clazz
     * @param method
     * @return
     */
    public String[] getMethodParameterNamesByAsm4(Class<?> clazz, final Method method) {
        final Class<?>[] parameterTypes = method.getParameterTypes();
        if (parameterTypes == null || parameterTypes.length == 0) {
            return null;
        }
        final Type[] types = new Type[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            types[i] = Type.getType(parameterTypes[i]);
        }
        final String[] parameterNames = new String[parameterTypes.length];

        String className = clazz.getName();
        int lastDotIndex = className.lastIndexOf(".");
        className = className.substring(lastDotIndex + 1) + ".class";
        // 直接从字节码中找参数名, 太狠了
        InputStream is = clazz.getResourceAsStream(className);
        try {
            ClassReader classReader = new ClassReader(is);
            classReader.accept(new ClassVisitor(Opcodes.ASM4) {
                @Override
                public MethodVisitor visitMethod(int access, String name, String desc, String signature,
                                                 String[] exceptions) {
                    // 只处理指定的方法
                    Type[] argumentTypes = Type.getArgumentTypes(desc);
                    if (!method.getName().equals(name) || !Arrays.equals(argumentTypes, types)) {
                        return null;
                    }
                    return new MethodVisitor(Opcodes.ASM4) {
                        @Override
                        public void visitLocalVariable(String name, String desc, String signature, Label start,
                                                       Label end, int index) {
                            // 静态方法第一个参数就是方法的参数，如果是实例方法，第一个参数是this
                            if (Modifier.isStatic(method.getModifiers())) {
                                parameterNames[index] = name;
                            } else if (index > 0) {
                                parameterNames[index - 1] = name;
                            }
                        }
                    };

                }
            }, 0);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return parameterNames;
    }

}
